﻿using System.Windows.Controls;
using System.Collections.Generic;
using DeepEarth.Core.Data;
using System.Windows;
using DeepEarth.Core;
using DeepEarth.Map.Core.Clustering;
using System;
using DeepEarth.Map.Core.Utilities;
using System.Windows.Media;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
namespace DeepEarth.Map.Core
{
    public class VectorLayer : UIElementLayer
    {
        const double BingMapsMaxLatitiude = 85.051128;
        const double GlobalLonWidth = 360;
        const double GlobalLatHeight = 85.051128 * 2;

        // Trial and error - not sure what the theoretical limit is here
        const double DefaultCelLogicalWidth = VectorCelLayer.DefaultCelWidth / GlobalCelLayer.DefaultGlobalWidth;

        // anything bigger than this will render inconsistently beyond zoom level 21;
        // This is a threshhold in degrees (This is not a logical width - logical width would be 1/32767)
        double maxBoundingBoxThreshhold = 0.010986663; // = 360 / 32767;

        // the width and height of each cel in degrees
        double celLonWidth;
        double celLatHeight;
        
        VectorDynamicLayer vdl;
        VectorDynamicLayer Vdl
        {
            get
            {
                if (vdl == null)
                {
                    vdl = new VectorDynamicLayer();
                    this.BaseChildren.Add(vdl);
                }

                return vdl;
            }
        }

        // If the maps are wrapped (e.g. Bing maps is wrapped)
        // modify behaviour of the static layer
        private bool mapIsWrapped = false;
        public bool MapIsWrapped
        {
            get { return mapIsWrapped; }
            set { mapIsWrapped = value; }
        }

        public VectorLayer()
        {
            celLonWidth = DefaultCelLogicalWidth * GlobalLonWidth;
            celLatHeight = DefaultCelLogicalWidth * GlobalLatHeight;
            cels = new List<VectorCelLayer>();
        }

        public override void Add(GeoItemViewModel viewModel, UIElement element)
        {
            if (viewModel.BoundingBox.MaxDimension > maxBoundingBoxThreshhold)
            {
                Vdl.Add(viewModel, element);

                ISelfPositioning sp = (ISelfPositioning)element;
                sp.LocationToPoint = vdl.LocationToViewportPoint;
                
            }
            else
            {
                var cel = GetCel(viewModel.BoundingBox);

                ISelfPositioning sp = (ISelfPositioning)element;
                sp.LocationToPoint = cel.LocationToCelPixel;
                sp.Refresh(new Point(0, 0), 16848);
                
                cel.Add(viewModel, element);

                SubscribeChanges(viewModel, element, cel);

            }
        }

        public void Add(LocationRectangle boundingBox, UIElement element)
        {
            if (boundingBox.MaxDimension > maxBoundingBoxThreshhold)
            {
                Vdl.Add(element);
            }
            else
            {
                var cel = GetCel(boundingBox);
                                
                ISelfPositioning sp = (ISelfPositioning)element;
                sp.LocationToPoint = cel.LocationToCelPixel;
                sp.Refresh(new Point(0, 0), 16848);
                cel.Add(element);                
            }
        }

        void CheckBounds()
        {
            LocationRectangle visibleBounds = MapInstance.BoundingRectangle;
        }

        public override void Remove(GeoItemViewModel viewModel, UIElement element)
        {
            UnsubscribeChanges(viewModel);

            if (vdl != null)
            {
                vdl.Remove(viewModel, element);
            }

            if (cels != null)
            {
                foreach (var cel in cels)
                {
                    cel.Remove(viewModel, element);                    
                }

                CheckCels();
            }

            base.Remove(viewModel, element);
        }

        public override void Clear()
        {
            if (vdl != null)
            {
                vdl.Clear();
            }

            if (cels != null)
            {
                foreach (var cel in cels)
                {
                    cel.Clear();
                }
            }

            cels.Clear();

            base.Clear();
        }


        protected override void SynchronizeOnFrame()
        {
        }

        protected override void SynchronizeOnEnd()
        {
        }

        public override void Refresh()
        {
            if (vdl != null)
            {
                vdl.Refresh();
            }

            if (cels != null)
            {
                foreach (var cel in cels)
                {
                    cel.Refresh();
                }
            }
        }

        #region Cell Management
        List<VectorCelLayer> cels;

        void AddCel(VectorCelLayer cel)
        {
            cels.Add(cel);
            this.BaseChildren.Add(cel);
        }

        void RemoveCel(VectorCelLayer cel)
        {
            cels.Remove(cel);
            this.BaseChildren.Remove(cel);
        }

        VectorCelLayer GetCel(LocationRectangle boundingBox)
        {
            foreach (var cel in cels)
            {
                if (cel.BoundingBox.Contains(boundingBox))
                    return cel;
            }

            var newCel = new VectorCelLayer();

            var left = Math.Min(180 - celLonWidth, boundingBox.West);
            var right = left + celLonWidth;

            var top = Math.Max(boundingBox.North, -BingMapsMaxLatitiude + celLatHeight);
            var bottom = top - celLatHeight;

            newCel.BoundingBox = new LocationRectangle(new Location(top, left), new Location(bottom, right));
            newCel.Origin = CoordinateTransformation.GeographicToLogical(new Location(top, left));
            newCel.LogicalWidth = DefaultCelLogicalWidth;

            AddCel(newCel);

            // we have a new cell, make sure it is initialized with the correct transforms applied;
            newCel.Synchronize();

            return newCel;
        }


        void CheckCel(VectorCelLayer cel)
        {
            if (!cel.Children.Any())
            {
                cels.Remove(cel);
                this.BaseChildren.Remove(cel);
            }
        }

        void CheckCels()
        {
            List<VectorCelLayer> deleteList = new List<VectorCelLayer>();

            foreach (var cel in cels)
            {
                if (!cel.Children.Any())
                {
                    deleteList.Add(cel);
                }
            }

            foreach (var cel in deleteList)
            {
                RemoveCel(cel);
            }
        }

        #endregion

        #region Cel Element Changed
        // Will probably need to revist this to ensure this cleans up correctly

        Dictionary<GeoItemViewModel, Subscription> subscriptions = new Dictionary<GeoItemViewModel, Subscription>();

        public class Subscription
        {
            IEnumerable<Location> Collection;
            NotifyCollectionChangedEventHandler CollectionChangedHandler;
            public PropertyChangedEventHandler LocationChangedHandler { get; private set; }

            public void Attach(IEnumerable<Location> collection, NotifyCollectionChangedEventHandler collectionChangedHandler, PropertyChangedEventHandler locationChangedHandler )
            {
                Collection = collection;
                CollectionChangedHandler = collectionChangedHandler;
                LocationChangedHandler = locationChangedHandler;

                if (collectionChangedHandler != null)
                {
                    ObservableCollection<Location> observable = collection as ObservableCollection<Location>;
                    observable.CollectionChanged += collectionChangedHandler;
                }

                foreach (Location l in collection)
                {
                    l.PropertyChanged += locationChangedHandler;
                }

            }

            public void Detach()
            {
                if (CollectionChangedHandler != null)
                {
                    ObservableCollection<Location> observable = Collection as ObservableCollection<Location>;
                    observable.CollectionChanged -= CollectionChangedHandler;
                }

                foreach (Location l in Collection)
                {
                    l.PropertyChanged -= LocationChangedHandler;
                }
            }
        }

        void SubscribeChanges(GeoItemViewModel viewModel, UIElement element, VectorCelLayer cel)
        {
            if (element is MapItemAdorner)
            {
                MapItemAdorner adorner = (MapItemAdorner)element;

                if (adorner.Content is IMultiLocation)
                {
                    IMultiLocation multiLoc = (IMultiLocation)adorner.Content;

                    PropertyChangedEventHandler locationChangedHandler = CreatePropertyChangedHandler(multiLoc, viewModel, element, cel);
                    NotifyCollectionChangedEventHandler collectionChangedHandler = null;

                    if (multiLoc.Locations is ObservableCollection<Location>)
                    {
                        collectionChangedHandler = CreateCollectionChangedHandler(viewModel);
                    }

                    Subscription subscription = new Subscription();
                    subscription.Attach(multiLoc.Locations, collectionChangedHandler, locationChangedHandler);
                    subscriptions.Add(viewModel, subscription);
                }
            }            
        }

        void UnsubscribeChanges(GeoItemViewModel viewModel)
        {
            if (subscriptions.ContainsKey(viewModel))
            {
                Subscription subscription = subscriptions[viewModel];
                subscription.Detach();
                subscriptions.Remove(viewModel);
            }
        }

        NotifyCollectionChangedEventHandler CreateCollectionChangedHandler(GeoItemViewModel viewModel)
        {
            return (o, e) =>
                {
                    Subscription s = subscriptions[viewModel];
                    if (e.OldItems != null)
                    {
                        foreach (Location l in e.OldItems)
                        {
                            l.PropertyChanged -= s.LocationChangedHandler;
                        }
                    }

                    if (e.NewItems != null)
                    {
                        foreach (Location l in e.NewItems)
                        {
                            l.PropertyChanged += s.LocationChangedHandler;
                        }

                        s.LocationChangedHandler(null, null);
                    }
                };
        }

        PropertyChangedEventHandler CreatePropertyChangedHandler(IMultiLocation multiLoc, GeoItemViewModel viewModel, UIElement element, VectorCelLayer cel)
        {
            return (o, e) =>
            {
                if (multiLoc.GetBoundingBox().MaxDimension > maxBoundingBoxThreshhold)
                {
                    if (cel.Children.Contains(element))
                    {
                        cel.Remove(viewModel, element);
                        CheckCel(cel);
                        this.Vdl.Add(viewModel, element);
                    }

                    UnsubscribeChanges(viewModel);
                }
            };
        }

        #endregion
    }
}
